<?
/*
 *  GGB - Gelin's Guest Book.
 *  Copyright (C) 2006-2007  Denis Nelubin aka Gelin
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  http://gelin.ru
 *  mailto:den@gelin.ru
 *
 *  Description:    Storage with SQL backend.
 *
 *  History:
 *  2006-06-25  Gelin   Initially created
 *  2006-12-31  Gelin   added getMessage()
 *  2007-01-06  Gelin   added setMessage()
 *  2007-01-07  Gelin   added deteleMessage()
 *  2007-01-07  Gelin   added getBans(),
 *  2007-01-20  Gelin   Changed postMessage() for new request headers handling
 *  2007-01-23  Gelin   Created getMessageHeaders()
 *  2007-02-04  Gelin   addBan() -> addBanRule()
 *  2007-02-04  Gelin   getBans() -> getBanRules()
 *  2007-02-04  Gelin   deleteBan() -> deleteBanRule()
 *  2007-02-04  Gelin   checkBan() -> getBanRule()
 *  2007-02-04  Gelin   Bug #1 Impossible to delete fields by admin (E-mail, for example)
 *                      - now empty string is considered as setting paramter to NULL
 *  2007-07-04  Gelin   Task #6 Add a posibility to view deleted messages in admin interface.
 *                      - added optional boolean argument "deleted" to
 *                        getMessagesForPage() and getNumberOfPages()
 *  2007-07-22  Gelin   Bug #5 Error on adding empty ban rule.
 *                      - added check for headers number
 *  2009-10-11  Gelin   Issue 7: Premoderation (with RSS feed of new messages)
 *                      - modified getting of messages: now by the type
 *                      - added premoderation check on posting the message
 */

require_once('lib/MessageType.php');
require_once('lib/MySQL.php');

/**
 *  Class, which controls all access to messages storage.
 */
class Storage {

    /** Database connection */
    var $db;

    /** Messages table name */
    var $messageTable;

    /** Ban table name */
    var $banTable;

    /** Message headers table name */
    var $headerTable;

    /** Reverse map of header keys to header names */
    var $checkHeaderName;

    /**
     *  Constructor.
     */
    function Storage() {
        global $config;
        $this->db = new MySQL();
        $this->messageTable = $config['db']['table']['message'];
        $this->banTable = $config['db']['table']['ban'];
        $this->headerTable = $config['db']['table']['header'];

        //make reverse map of header keys to header names
        foreach ($config['check_header'] as $key => $value) {
            $this->checkHeaderName[$value['key']] = $key;
        }

    }

    /**
     *  Returns number of pages which is need to show messages.
     *  @param  $messageType    optional, indicates that type of messages we want to view
     *  @return number of pages
     */
    function getNumberOfPages($messageType = APPROVED_MESSAGE) {
        global $config;
        $query = 'SELECT count(*) from '.$this->messageTable;
        $query .= $this->_whereMessageType($messageType);
        $result = $this->db->query($query);
        $row = $this->db->row($result);
        $numberOfMessages = $row[0];
        $numberOfPages = ceil($numberOfMessages / $config['pages']['messages_on_page'] * 1.0);
        return $numberOfPages;
    }

    /**
     *  Returns array of messages which should be displayed on specified page.
     *  @param  $pageNumber number of page
     *  @param  $messageType    optional, indicates that type of messages we want to view
     *  @return array of messages
     */
    function getMessagesForPage($pageNumber, $messageType = APPROVED_MESSAGE) {
        global $config;
        $messages = array();
        $query = 'SELECT * from '.$this->messageTable;
        $query .= $this->_whereMessageType($messageType);
        $query .= ' ORDER BY post_time desc limit ?,?';
        $numberOfMessages = $config['pages']['messages_on_page'];
        $fromMessage = $numberOfMessages * $pageNumber;
        $result = $this->db->query($query, $fromMessage, $numberOfMessages);
        while ($message = $this->db->row($result)) {
            $messages[] = $message;
        }
        return $messages;
    }

    /**
     *  Returns where clause to select the specified message type.
     */
    function _whereMessageType($messageType) {
        switch ($messageType) {
        case NEW_MESSAGE:
            return ' WHERE approve_time is null AND delete_time is null';
            //return ' WHERE delete_time is null';
        case APPROVED_MESSAGE:
            return ' WHERE approve_time is not null AND delete_time is null';
            //return ' WHERE delete_time is null';
        case DELETED_MESSAGE:
            return ' WHERE delete_time is not null';
        default:
            return '';
        }
    }

    /**
     *  Adds message.
     *  If premoderation is disabled, the approve time of the message
     *  is set the same as the post time.
     *  @param  $message    new message to add
     */
    function postMessage($message) {
        global $config;
        //insert message
        $query = 'INSERT into '.$this->messageTable.
                ' (post_time, approve_time, name, email, url, message)'.
                ' values (?,?,?,?,?,?)';
        $this->db->query($query,
                $message['post_time'],
                $config['behaviour']['premoderation'] ? NULL : $message['post_time'],
                $this->_emptyIsNull($message['name']),
                $this->_emptyIsNull($message['email']),
                $this->_emptyIsNull($message['url']),
                $this->_emptyIsNull($message['message']));
        //insert headers
        $id = $this->db->insertedID();
        $query = 'INSERT into '.$this->headerTable.
                ' (message_id, header_id, value) values (?,?,?)';
        foreach ($message['headers'] as $key => $value) {
            $this->db->query($query,
                    $id,
                    $config['check_header'][$key]['key'],   //database key for headers
                    $value['value']);
        }
    }

    /**
     *  Returns message with specified ID.
     *  @param  $messageID ID of message
     *  @return message
     */
    function getMessage($messageID) {
        global $config;
        $message = NULL;
        $query = 'SELECT * from '.$this->messageTable.' WHERE id = ?';
        $result = $this->db->query($query, $messageID);
        $message = $this->db->row($result);
        return $message;
    }

    /**
     *  Updates message.
     *  @param  $message    new message to add
     */
    function setMessage($message) {
        $query = 'UPDATE '.$this->messageTable.' SET';
        $params = array();
        $this->_addSetParam($message, 'name', $query, $params);
        $this->_addSetParam($message, 'email', $query, $params);
        $this->_addSetParam($message, 'url', $query, $params);
        $this->_addSetParam($message, 'message', $query, $params);
        $this->_addSetParam($message, 'reply_time', $query, $params);
        $this->_addSetParam($message, 'reply', $query, $params);

        $query .= ' WHERE id = ?';
        $this->db->query($query,
                $params,
                $message['id']);
    }

    /**
     *  Deletes message with specified ID (sets delete_time to the current time).
     *  @param  $messageID ID of message
     */
    function deleteMessage($messageID) {
        global $config;
        $query = 'UPDATE '.$this->messageTable.' SET delete_time = ? WHERE id = ?';
        $result = $this->db->query($query, time(), $messageID);
    }

    /**
     *  Approves message with specified ID (sets approve_time to the current time).
     *  @param  $messageID ID of message
     */
    function approveMessage($messageID) {
        global $config;
        $query = 'UPDATE '.$this->messageTable.' SET approve_time = ? WHERE id = ?';
        $result = $this->db->query($query, time(), $messageID);
    }

    /**
     *  Added SET clause to SQL statement when updating message.
     */
    function _addSetParam($message, $paramName, &$query, &$queryParams) {
        if (isset($message[$paramName])) {
            if (count($queryParams) > 0) {
                $query .= ',';
            }
            $query .= ' '.$paramName.' = ?';
            //empty string become NULL
            $queryParams[] = $this->_emptyIsNull($message[$paramName]);
        }
    }

    /**
     *  Return NULL if value is empty string.
     */
    function _emptyIsNull($value) {
        return $value == '' ? NULL : $value;
    }

    /**
     *  Returns array of ban rules.
     *  @return array of ban rules
     */
    function getBanRules() {
        global $config;
        $bans = array();
        $query = 'SELECT * from '.$this->banTable.' ORDER BY id';
        $result = $this->db->query($query);
        while ($row = $this->db->row($result)) {
            $this->_addBanRow($bans, $row);
        }
        return $bans;
    }

    /**
     *  Adds new ban rule.
     *  @param  $ban    new ban to add
     */
    function addBanRule($ban) {
        global $config;
        $query = 'INSERT into '.$this->banTable.
                ' (id, header_id, value, create_time) values (?,?,?,?)';
        $id = NULL;
        if (count($ban['headers']) > 0) {
            foreach ($ban['headers'] as $key => $header) {
                $this->db->query($query,
                        $id,
                        $config['check_header'][$key]['key'],
                        $header['value'],
                        $ban['create_time']);
                if ($id == NULL) {
                    $id = $this->db->insertedID();
                }
            }
        }
    }

    /**
     *  Deletes ban rule.
     *  @param  $ban    ban rule to delete (only ID is requred)
     */
    function deleteBanRule($ban) {
        $query = 'DELETE from '.$this->banTable.' WHERE id = ?';
        $this->db->query($query,
                $ban['id']);
    }

    /**
     *  Gets ban rules which can be applied to specified header.
     *  @param  $header header for search
     *  @return array of ban rules
     */
    function getBanRule($header) {
        global $config;
        $rules = array();
        $query = 'SELECT b.id id, b.header_id header_id, b.value value from '.$this->banTable.' a, '.$this->banTable.' b '.
                'WHERE a.header_id = ? and a.value = ? and a.id = b.id';
        $result = $this->db->query($query,
                                $config['check_header'][$header['header_id']]['key'],
                                $header['value']);
        while ($row = $this->db->row($result)) {
            $this->_addBanRow($rules, $row);
        }
        return $rules;
    }

    /**
     *  Extracts headers of specified message from database.
     *  @param  $messageID  ID of a message
     *  @return array of headers
     */
    function getMessageHeaders($messageID) {
        global $config;
        $headers = array();
        $query = 'SELECT * from '.$this->headerTable.' WHERE message_id = ?';
        $result = $this->db->query($query, $messageID);
        while ($header = $this->db->row($result)) {
            $name = $this->checkHeaderName[$header['header_id']];
            $headers[$name]['value'] = $header['value'];
        }
        return $headers;
    }

    /**
     *  Adds row selected from ban rules table to array of ban rules.
     */
    function _addBanRow(&$rules, $row) {
        $rules[$row['id']]['id'] = $row['id'];
        $rules[$row['id']]['create_time'] = $row['create_time'];
        $name = $this->checkHeaderName[$row['header_id']];
        $rules[$row['id']]['headers'][$name]['value'] = $row['value'];
    }

}

?>